#ifndef LOGGER_H
#define LOGGER_H
#pragma warning(disable:4996)

#include <windows.h>
#include <fstream>
#include <string>
#include <iostream>
#include <cstdarg>
#include <cstdio>

#include <boost/serialization/access.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/map.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>

using namespace std;

const int H_RAW_DATA = 0;
const int H_IND_SUB_VECTOR = 1;
const int H_IND_L2NORM = 2;
const int H_IND_MEDIAN = 3;
const int H_IND_HASHMAP = 4;
const int S_RAW_DATA = 5;
const int S_MATCH_PNT = 6;
const int S_MATCH_IMG = 7;
const int S_MATCH_VID = 8;
const int H_RAW_IMG_HELPER = 9;
const int S_QUERY_IMG_HELPER = 10;
const int S_QUERY_VID_HELPER = 11;
const int S_READABLE_RES = 12;
const int STATE_NUM = 13;

class BreakInfo {
	friend boost::serialization::access;
	template<class Archive>
	void serialize(Archive & ar, const unsigned int version)
	{
		ar & isHash;
		ar & processId;
		ar & blockId;
		ar & fileInfo;
	}
public :
	BreakInfo(){}
	~BreakInfo(){}

	bool isHash;
	int processId;
	unsigned int blockId;
	vector<vector<string> > fileInfo;
};

class BreakLogger {
private:
	string _szFile;
	vector<map<string, unsigned char> > filepool;
public :
	BreakInfo * info;
	BreakLogger(char * szfile) {
		_szFile = szfile;
		fstream fs(_szFile.c_str(), ios::binary);
		info = new BreakInfo();
		filepool.resize(STATE_NUM);
		for (unsigned int i = 0; i < STATE_NUM; i++) {
			map<string, unsigned char> tmp;
			filepool[i] = tmp;
		}
		if (!LoadBreakInfo()) {
			info->processId = H_RAW_DATA;
			info->blockId = 0;
			info->isHash = true;
			info->fileInfo.resize(STATE_NUM);
			for (unsigned int i = 0; i < STATE_NUM; i++) {
				vector<string> tmp;
				info->fileInfo.at(i) = tmp;
			}
			SaveBreakInfo();
		}
	}
	
	~BreakLogger() {
		delete info;
	}

	void SetToDoFlag() {
		info->blockId ++;
		SaveBreakInfo();
	}
	void UpdateBreakInfo(const int key, const string & val, bool blockFlag) { 
		if (filepool[key].find(val) == filepool[key].end()) {
			filepool[key][val] = 0;
			info->fileInfo[key].push_back(val);
		}
		if (blockFlag) {
			info->blockId ++;
		}
		SaveBreakInfo();
	}

	bool HasBreakPoint() {
		if (info->blockId != info->fileInfo[info->processId].size()) {
			return true;
		}
		return false;
	}

	void SetNewState(int pId) {
		info->processId = pId;
		info->blockId = 0;
		SaveBreakInfo();
	}

	bool SaveBreakInfo() {
		ofstream ofs(_szFile.c_str(), ios::binary);
		boost::archive::binary_oarchive oa(ofs);
		oa << *info;
		ofs.close();
		return true;
	}

	bool LoadBreakInfo() {
		ifstream ifs(_szFile.c_str(), ios::binary);
		if (ifs.good()) {
			boost::archive::binary_iarchive ia(ifs);
			ia >> *info;
			ifs.close();

			for (unsigned int i = 0; i < info->fileInfo.size(); i++) {
				for (unsigned int j = 0; j < info->fileInfo[i].size(); j++) {
					filepool[i][info->fileInfo[i][j]] = 0;
				}
			}
			return true;
		}
		return false;
	}

	bool DeleteBreakInfo() {
		fstream fs(_szFile.c_str(), ios::binary);
		if (fs.good()) {
			remove(_szFile.c_str());
		}
	}
};

class SyncLogger
{
private:
	bool _bConsolePrint;
	HANDLE _hLogFile;
	HANDLE _hLoggerThread;
	DWORD _dwLoggerThreadId;

	static DWORD WINAPI SyncLoggerThread(SyncLogger *pthis) {
		MSG msg;
		DWORD dw;

		PeekMessage(&msg,0,0,0,PM_NOREMOVE);
		while (GetMessage(&msg,0,0,0)) {
			switch (msg.message) {
				case WM_USER:
				WriteFile(pthis->_hLogFile, (char *)msg.lParam, (DWORD)strlen((char *)msg.lParam), &dw, 0);
				delete (char *)msg.lParam;
			}
		}

		return 0;
	}

public:
	SyncLogger(const char *pszFilename, bool cp) {
		_hLogFile=CreateFile(pszFilename,GENERIC_WRITE,FILE_SHARE_READ,0,OPEN_ALWAYS,0,0);
		if (_hLogFile) {
			SetFilePointer(_hLogFile,0,0,FILE_END);
			_hLoggerThread=CreateThread(0,0,(LPTHREAD_START_ROUTINE)SyncLoggerThread,this,0,&_dwLoggerThreadId);
			_bConsolePrint = cp;
		}
	}
	~SyncLogger() {
		if (_hLoggerThread) {
			PostThreadMessage(_dwLoggerThreadId,WM_QUIT,0,0);
			WaitForSingleObject(_hLoggerThread,INFINITE);
			CloseHandle(_hLoggerThread);
		}
		if (_hLogFile) CloseHandle(_hLogFile);
	}
	void Log(const char *pszFmt, ...) {
		char *psz;
		char pszText[512];
		DWORD dwDateLen, dwTimeLen;

		va_list args;
		va_start(args, pszFmt);
		vsprintf(pszText, pszFmt, args);
		va_end(args);

		if (_hLogFile && _dwLoggerThreadId && pszText) {
			dwDateLen=GetDateFormat(LOCALE_SYSTEM_DEFAULT,DATE_SHORTDATE,0,0,0,0);
			dwTimeLen=GetTimeFormat(LOCALE_SYSTEM_DEFAULT,0,0,0,0,0);
			size_t buflen = dwDateLen+dwTimeLen+strlen(pszText)+5;
			psz=new char[buflen];
			psz[0]='[';
			GetDateFormat(LOCALE_SYSTEM_DEFAULT,DATE_SHORTDATE,0,0,psz+1,dwDateLen);
			psz[dwDateLen]=' ';
			GetTimeFormat(LOCALE_SYSTEM_DEFAULT,0,0,0,psz+dwDateLen+1,dwTimeLen);
			strcat_s(psz, buflen, "] ");
			strcat_s(psz, buflen, pszText);
			strcat_s(psz, buflen, "\r\n");
			if (_bConsolePrint) std::cout << psz;
			while (!PostThreadMessage(_dwLoggerThreadId,WM_USER,0,(LPARAM)psz)) Sleep(0);
		}
	}
};
#endif